package main

import (
	"fmt"
	"goshp"
	"math"
)

func getPolygon(s []byte) *goshp.Polygon {

	//fmt.Printf("record number:%v\n", bigInt32(s[0:4]))
	//fmt.Printf("record length:%v\n", bigInt32(s[4:8]))
	//fmt.Printf("shape type:%v\n", littleInt32(s[8:12]))

	shape := new(goshp.Polygon)

	shape.RecordNumber = bigInt32(s[0:4])
	//fmt.Printf("record number:%v\n", shape.RecordNumber)

	// Skip Record Header (8 bytes)
	s = s[8:]

	// Read BBox values
	shape.Box.Xmin = math.Float64frombits(littleUInt64(s[4:12]))
	shape.Box.Ymin = math.Float64frombits(littleUInt64(s[12:20]))
	shape.Box.Xmax = math.Float64frombits(littleUInt64(s[20:28]))
	shape.Box.Ymax = math.Float64frombits(littleUInt64(s[28:36]))
	shape.NumParts = littleInt32(s[36:40])
	shape.NumPoints = littleInt32(s[40:44])

	//fmt.Printf("num parts:%#v\n", shape.NumParts)
	//fmt.Printf("num points:%#v\n", shape.NumPoints)

	// Now itare over parts
	parts := make([]int32, shape.NumParts)

	var offset int
	for i := 0; i < int(shape.NumParts); i++ {
		//fmt.Printf("i:%v\n", i)
		offset = 44 + (i * 4)
		//fmt.Printf("offset:%v\n", offset)
		parts[i] = littleInt32(s[offset : offset+4])
	}

	shape.Parts = parts

	// And itare over parts

	points := make([]goshp.Point, shape.NumPoints)

	pointsOffset := offset + 4
	for i := 0; i < int(shape.NumPoints); i++ {
		//fmt.Printf("i:%v\n", i)
		offset = pointsOffset + (i * 8)
		x := math.Float64frombits(littleUInt64(s[offset : offset+8]))
		offset = pointsOffset + (i * 8) + 8
		y := math.Float64frombits(littleUInt64(s[offset : offset+8]))
		points[i] = goshp.Point{x, y}
	}

	shape.Points = points

	//fmt.Printf("s:%#v\n", shape)

	if littleInt32(s[0:4]) == goshp.POLYGON {
		//fmt.Printf("type:=%#v\n", "Polygon")
	}

	/*
		for _, p := range shape.Points {
			fmt.Printf("%v\n", p.String())
		}
	*/

	return shape
}

func fileHeader(s []byte) *goshp.FileHeader {
	fh := new(goshp.FileHeader)
	fh.FileCode = bigInt32(s[0:4])
	fh.FileLength = bigInt32(s[24:28])
	fh.Version = littleInt32(s[28:32])
	fh.ShapeType = littleInt32(s[32:36])
	fh.Xmin = math.Float64frombits(littleUInt64(s[36:44]))
	fh.Ymin = math.Float64frombits(littleUInt64(s[44:52]))
	fh.Xmax = math.Float64frombits(littleUInt64(s[52:60]))
	fh.Ymax = math.Float64frombits(littleUInt64(s[60:68]))
	fh.Zmin = math.Float64frombits(littleUInt64(s[68:76]))
	fh.Zmax = math.Float64frombits(littleUInt64(s[76:84]))
	fh.Mmin = math.Float64frombits(littleUInt64(s[84:92]))
	fh.Mmax = math.Float64frombits(littleUInt64(s[92:100]))

	//fmt.Printf("s:%v\n", fh)
	return fh
}


func littleInt32(b []byte) int32 {
	return int32(uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24)

}

func bigInt32(b []byte) int32 {
	return int32(uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24)
}

func littleUInt64(b []byte) uint64 {
	return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
}


func readShapeIndex() {
	s, err := goshp.ReadFile("/home/aydink/Downloads/england/waterways.shx")
	//fmt.Printf("len s=%v\n", len(s))

	if err != nil {
		fmt.Println(err)
		return
	}

	s1, err1 := goshp.ReadFile("/home/aydink/Downloads/england/waterways.shp")
	fmt.Printf("len s=%v\n", len(s))

	if err1 != nil {
		fmt.Println(err1)
	}

	// create goshp.FileHeader from byte slice	
	sh := fileHeader(s[0:100])

	fileLength := int(sh.FileLength * 2)

	if fileLength != len(s) {
		fmt.Println("sh.FileLenght doesn't match the actual file size")
		return
	}

	// calculate total number of records
	numberOfRecords := (fileLength - 100) / 8
	fmt.Printf("numberOfRecords=%#v\n", numberOfRecords)

	// create slice to keep polygons
	shapes := make([]*goshp.Shape, numberOfRecords)

	// start itarating index file
	offset := 100

	for i := 0; i < numberOfRecords; i++ {
		offset = 100 + (i * 8)
		recordOffset := bigInt32(s[offset : offset+4])
		contentLength := bigInt32(s[offset+4 : offset+8])
		//fmt.Printf("offset=%v content length=%v\n", recordOffset, contentLength)
		p := readShape(s1[recordOffset*2 : (recordOffset*2)+(contentLength*2)])

		shapes[i] = p
		fmt.Printf("%v\t%#v\n", p.RecordNumber, p.NumParts)
		if i%1000 == 0 {
			fmt.Printf("%v\n", i)
		}
	}

	fmt.Printf("%v poygon created in memory\n", len(shapes))
	fmt.Printf("%#v\n", shapes[82])
}

func main() {

	readShapeIndex()
	return

	s, err := goshp.ReadFile("/home/aydink/Downloads/shape/test.shp")
	fmt.Printf("len s=%v\n", len(s))

	if err != nil {
		fmt.Println(err)
	}

	fileHeader(s[0:100])
	getPolygon(s[100 : 100+(996*2)])

	p := new(goshp.Point)
	p.X = 32
	p.Y = 40
	//fmt.Printf("s:%v\n", p.Serialize())
	fmt.Printf("%v\n", p)
}

func readShape(s []byte) *goshp.Shape {

	fmt.Printf("record number:%v\n", bigInt32(s[0:4]))
	fmt.Printf("record length:%v\n", bigInt32(s[4:8]))
	fmt.Printf("shape type:%v\n", littleInt32(s[8:12]))

	shape := new(goshp.Shape)

	shape.RecordNumber = bigInt32(s[0:4])
	//fmt.Printf("record number:%v\n", shape.RecordNumber)
	shape.ShapeType = littleInt32(s[8:12])
	//fmt.Printf("shape type:%v\n", littleInt32(s[8:12]))

	// Skip Record Header (8 bytes)
	s = s[8:]

	// cursorLocation is used to track global offset from the beginning of Shape record 
	offset := 0

	if shape.ShapeType == goshp.POINT || shape.ShapeType == goshp.POINT_Z || shape.ShapeType == goshp.POINT_M {

	}

	if (shape.ShapeType == goshp.POLY_LINE) || (shape.ShapeType == goshp.POLYGON) || (shape.ShapeType == goshp.MULTI_PATCH) {
		// Read BBox values
		shape.Box.Xmin = math.Float64frombits(littleUInt64(s[4:12]))
		shape.Box.Ymin = math.Float64frombits(littleUInt64(s[12:20]))
		shape.Box.Xmax = math.Float64frombits(littleUInt64(s[20:28]))
		shape.Box.Ymax = math.Float64frombits(littleUInt64(s[28:36]))
		shape.NumParts = littleInt32(s[36:40])
		shape.NumPoints = littleInt32(s[40:44])

		fmt.Printf("NumParts=%v\n", shape.NumParts)
		fmt.Printf("NumPoints=%v\n", shape.NumPoints)
		
		// Now itare over parts
		parts := make([]int32, shape.NumParts)

		for i := 0; i < int(shape.NumParts); i++ {
			//fmt.Printf("i:%v\n", i)
			offset = 44 + (i * 4)
			//fmt.Printf("offset:%v\n", offset)
			parts[i] = littleInt32(s[offset : offset+4])
		}

		// cursor is now at the end of the beginnig of the "Parts" value
		offset = offset + 4

		shape.Parts = parts

		// And itare over parts
		points := make([]goshp.Point, shape.NumPoints)

		offset= 44 + (4 * int(shape.NumParts))
		for i := 0; i < int(shape.NumPoints); i++ {
	
			offset = offset + (i * 8)
			fmt.Printf("i:%v  offset:%v\n", i, offset)
			
			x := math.Float64frombits(littleUInt64(s[offset : offset+8]))
			//offset = offset + (i * 8) + 8
			
			offset = offset + 8
			fmt.Printf("i:%v  offset:%v\n", i, offset)
			
			y := math.Float64frombits(littleUInt64(s[offset : offset+8]))
			points[i] = goshp.Point{x, y}
			
		}

		shape.Points = points

	}

	if (shape.ShapeType == goshp.POLY_LINE_M) || (shape.ShapeType == goshp.POLYGON_M) || (shape.ShapeType == goshp.MULTI_PATCH) {

		// cursor is now at the end of the beginnig of the "Mmin" value
		// now read Mmin, Max
		offset = offset + 8		
		shape.Mmin = math.Float64frombits(littleUInt64(s[offset : offset+8]))
		offset = offset + 8
		shape.Mmax = math.Float64frombits(littleUInt64(s[offset : offset+8]))

		// and read Marray values
		offset = offset + 8		

		// allocate mArray slice to keep Marray values
		mArray := make([]float64, shape.NumPoints)

		// iterate over Marray values
		for i := 0; i < int(shape.NumPoints); i++ {
			//fmt.Printf("i:%v\n", i)
			offset = offset + (i * 8)
			//fmt.Printf("offset:%v\n", offset)
			mArray[i] = math.Float64frombits(littleUInt64(s[offset : offset+8]))
		}

		// assign Marray
		shape.Marray = mArray
	}

	//fmt.Printf("num parts:%#v\n", shape.NumParts)
	//fmt.Printf("num points:%#v\n", shape.NumPoints)

	fmt.Printf("s:%#v\n", shape)

	return shape
}
